﻿using System.Linq;

namespace LogicStudio.Framework.Auditoria
{
    using Excepciones;
    using System;
    using System.Diagnostics;
    using System.Reflection;
    using System.Text;
    using System.Collections.Generic;
    using System.Data.Objects.DataClasses;
    using System.Data;
    using System.Data.Objects;
    using Utilidades;
    using System.Configuration;
    
    using System.Web;

    /// <summary>
    /// Clase base para el manejo de auditoría
    /// </summary>
    public class AuditoriaBase
    {
        #region Atributos

        private static Guid _auditoriaId;

        #endregion

        #region Constantes

        private const string ParametroIpRemota = "REMOTE_ADDR";

        #endregion

        #region Clases Internas

        internal class InfoCredenciales
        {
            public string Usuario { get; set; }
            public string Ip { get; set; }
        }

        #endregion

        #region Enumeradores

        internal class ListaAccciones
        {
            public const string Insercion = "Inserción";
            public const string Eliminacion = "Eliminación";
            public const string Actualizacion = "Actualización";
            public const string Procedimiento = "Procedimiento";
        }

        #endregion

        #region Métodos Privados

        /// <summary>
        /// Obtenemos los datos para la áuditoria
        /// </summary>
        /// <returns>
        /// Un objeto del tipo <see cref="InfoCredenciales"/> que contiene la información de la sesion
        /// </returns>
        private static InfoCredenciales ObetenerDatosAuditoria()
        {
            try
            {
                var datos = new InfoCredenciales
                {
                    Usuario = HttpContext.Current.User.Identity.Name,
                    Ip = HttpContext.Current.Request.ServerVariables[ParametroIpRemota]
                };
                if (string.IsNullOrEmpty(datos.Usuario))
                {
                    datos.Usuario = HttpContext.Current.Session["Usuario"] as string;
                    datos.Ip = HttpContext.Current.Session["Ip"] as string;
                }
                
                return datos;
            }
            catch (Exception)
            {
                return new InfoCredenciales();
            }
        }

        /// <summary>
        /// Método para realizar  la auditoria
        /// </summary>
        /// <param name="contexto">Contexto Actual</param>
        /// <param name="objetos">Lista de objetos que contienen los valores originales</param>
        /// <param name="isAuditoriaDetalle">Indica si guarda la AuditoriaDetalle</param>
        /// <param name="usuario">Usuario Actual</param>
        /// <param name="direccionIp">Ip del Cliente </param>
        /// <param name="mensajeError">Mensaje de Error</param>
        private static void Procesar(ObjectContext contexto, List<object> objetos, bool isAuditoriaDetalle,
                                    string usuario, string direccionIp, string mensajeError)
        {
            try
            {
                if (string.IsNullOrEmpty(mensajeError))
                {
                    GenerarTablas();
                    var changes = contexto.ObjectStateManager.GetObjectStateEntries
                        (EntityState.Added | EntityState.Deleted | EntityState.Modified);
                    using (var contextoFramework = new ContextoFramework())
                    {
                        var indice = 0;
                        foreach (var entrada in changes)
                        {
                            if (!entrada.IsRelationship && entrada.Entity != null && !(entrada.Entity is Auditoria))
                            {
                                var audit = ProcesarAuditoria(entrada, usuario, direccionIp, mensajeError);
                                contextoFramework.AddToAuditoria(audit);
                                if (Convert.ToBoolean(isAuditoriaDetalle))
                                {
                                    if (entrada.State != EntityState.Added)
                                    {
                                        var objeto = objetos[indice];
                                        var auditoriaDet = ProcesarAuditoriaDetalle(entrada, objeto, usuario);
                                        contextoFramework.AddToAuditoriaDetalle(auditoriaDet);
                                        indice++;
                                    }
                                    else
                                    {
                                        var auditoriaDet = ProcesarAuditoriaDetalle(entrada, entrada, usuario);
                                        contextoFramework.AddToAuditoriaDetalle(auditoriaDet);
                                    }
                                }
                            }
                        }
                        contextoFramework.SaveChanges();
                    }
                }
                else
                {
                    ProcesarAuditoriaError(usuario, direccionIp, mensajeError);
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Recursos.MensajeAuditoria, ex.Message);
            }
        }

        /// <summary>
        /// Devuelve los datos de la entidad auditoria
        /// </summary>
        /// <param name="objeto">Estado actual de la entidad </param>
        /// <param name="usuario">Usuario Logueado</param>
        /// <param name="direccionIp">Ip del cliente </param>
        /// <param name="mensajeError">Error producido</param>
        /// <returns>Objeto de tipo Auditoria</returns>
        private static Auditoria ProcesarAuditoria(ObjectStateEntry objeto, string usuario, string direccionIp, string mensajeError)
        {
            try
            {
                var auditoria = new Auditoria { Usuario = usuario, FechaHora = DateTime.Now };
                _auditoriaId = Guid.NewGuid();
                auditoria.AuditoriaId = _auditoriaId;
                var accion = string.Empty;
                if (objeto.State == EntityState.Added)
                {
                    accion = ListaAccciones.Insercion;
                }
                if (objeto.State == EntityState.Deleted)
                {
                    accion = ListaAccciones.Eliminacion;
                }
                if (objeto.State == EntityState.Modified)
                {
                    accion = ListaAccciones.Actualizacion;
                }
                auditoria.Accion = accion;
                auditoria.Descripcion = accion + ": " + objeto.EntitySet.Name;
                auditoria.DireccionIP = direccionIp;
                auditoria.MensajeError = mensajeError;
                return auditoria;
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Recursos.MensajeAuditoria, ex.Message);
                return null;
            }
        }

        /// <summary>
        /// Devuelve los Datos de la Entidad AuditoriaDetalle
        /// </summary>
        /// <param name="objeto">Estado actual de la entidad</param>
        /// <param name="objetoOriginal">Entidad con los datos originales</param>
        /// <param name="usuario">Usuario Logueado</param>
        /// <returns>Objeto tipo AuditoriaDetalle</returns>
        private static AuditoriaDetalle ProcesarAuditoriaDetalle(ObjectStateEntry objeto, object objetoOriginal, string usuario)
        {
            try
            {
                var auditoria = new AuditoriaDetalle
                {
                    Usuario = usuario,
                    AuditoriaId = _auditoriaId,
                    DatosNuevos = ObtenerDatos(objeto)
                };

                if (objeto.State == EntityState.Added)
                {
                    auditoria.Accion = ListaAccciones.Insercion;
                }
                if (objeto.State == EntityState.Deleted)
                {
                    auditoria.Accion = ListaAccciones.Eliminacion;
                }
                if (objeto.State == EntityState.Modified)
                {
                    auditoria.Accion = ListaAccciones.Actualizacion;
                    auditoria.DatosAnteriores = ObtenerDatosOriginales(objeto, objetoOriginal);
                }
                auditoria.Tabla = objeto.EntitySet.Name;
                return auditoria;
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Recursos.MensajeAuditoria, ex.Message);
                return null;
            }
        }

        /// <summary>
        /// Retorna una cadena en un formato xml con los nuevos datos de la entidad
        /// </summary>
        /// <param name="objeto">Estado de la Entidad</param>
        /// <returns>Cadena de texto</returns>
        private static string ObtenerDatos(ObjectStateEntry objeto)
        {
            var cadena = new StringBuilder();
            if (objeto.Entity is EntityObject)
            {
                if (objeto.State != EntityState.Added)
                {
                    foreach (var item in objeto.EntityKey.EntityKeyValues)
                    {
                        cadena.Append(item.Key);
                        cadena.Append(" = ");
                        cadena.Append(item.Value);
                        cadena.Append(", ");
                    }
                    foreach (var propiedad in objeto.GetModifiedProperties())
                    {
                        cadena.Append(propiedad);
                        cadena.Append(" = ");
                        cadena.Append(objeto.CurrentValues[propiedad]);
                        cadena.Append(", ");
                    }
                }
                else
                {
                    for (var i = 0; i < objeto.CurrentValues.FieldCount; i++)
                    {
                        cadena.Append(objeto.CurrentValues.GetName(i));
                        cadena.Append(" = ");
                        cadena.Append(objeto.CurrentValues[i]);
                        cadena.Append(", ");
                    }
                }
                return cadena.ToString();
            }
            return null;
        }

        /// <summary>
        /// Devuelve una cadena con los datos originales de la entidad
        /// </summary>
        /// <param name="objeto">Estado de la Entidad</param>
        /// <param name="objetoOriginal">Entidad con los datos originales</param>
        /// <returns>Cadena de texto</returns>
        private static string ObtenerDatosOriginales(ObjectStateEntry objeto, object objetoOriginal)
        {
            var cadena = new StringBuilder();
            if (objeto.Entity is EntityObject)
            {
                foreach (var item in objeto.EntityKey.EntityKeyValues)
                {
                    cadena.Append(item.Key);
                    cadena.Append(" = ");
                    cadena.Append(item.Value);
                    cadena.Append(", ");
                }

                foreach (var propiedad in objeto.GetModifiedProperties())
                {
                    cadena.Append(propiedad);
                    cadena.Append(" = ");
                    var propiedadInfo = objetoOriginal.GetType().GetProperty(propiedad);
                    var dato = propiedadInfo.GetValue(objetoOriginal, null);
                    if (dato != null)
                    {
                        cadena.Append(dato.ToString());
                        cadena.Append(", ");
                    }
                }
                return cadena.ToString();
            }
            return null;
        }

        /// <summary>
        /// Método llamado en caso de ocurrir un error
        /// </summary>
        /// <param name="usuario">Usuario Logueado</param>
        /// <param name="direccionIp">Ip del cliente</param>
        /// <param name="mensajeError">Mensaje de Error</param>
        private static void ProcesarAuditoriaError(string usuario, string direccionIp, string mensajeError)
        {
            try
            {
                using (var contextoFramework = new ContextoFramework())
                {
                    var auditoria = new Auditoria
                    {
                        Usuario = usuario,
                        FechaHora = DateTime.Now,
                        AuditoriaId = Guid.NewGuid(),
                        Accion = string.Empty,
                        Descripcion = string.Empty,
                        DireccionIP = direccionIp,
                        MensajeError = mensajeError
                    };
                    contextoFramework.AddToAuditoria(auditoria);
                    contextoFramework.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Recursos.MensajeAuditoria, ex.Message);
            }
        }

        /// <summary>
        /// Genera las tablas para la Auditoria en caso de no existir en la Base de Datos
        /// </summary>
        private static void GenerarTablas()
        {
            try
            {
                using (var contextoFramework = new ContextoFramework())
                {
                    contextoFramework.ExecuteStoreCommand(ContenedorScript.Auditoria);
                }
            }
            catch (Exception exception)
            {

                ControladorExcepciones.Procesar(exception);
            }
        }

        #endregion

        #region Métodos Publicos

        /// <summary>
        /// Realiza la auditoria a partir de un procedimiento almacenado
        /// </summary>
        /// <param name="nombreProcedimiento">Nombre del Procedimiento</param>
        /// <param name="mensajeError">Mensaje de Error</param>
        public static void ProcesarAuditoria(string nombreProcedimiento, string mensajeError)
        {
            try
            {
                GenerarTablas();
                var datosAuditoria = ObetenerDatosAuditoria();
                var auditoria = new Auditoria { Usuario = datosAuditoria.Usuario, FechaHora = DateTime.Now };
                _auditoriaId = Guid.NewGuid();
                auditoria.AuditoriaId = _auditoriaId;
                auditoria.Accion = ListaAccciones.Procedimiento;
                auditoria.Descripcion = ListaAccciones.Procedimiento + ": " + nombreProcedimiento;
                auditoria.DireccionIP = datosAuditoria.Ip;
                auditoria.MensajeError = mensajeError;

                using (var contextoFramework = new ContextoFramework())
                {
                    contextoFramework.AddToAuditoria(auditoria);
                    contextoFramework.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Recursos.MensajeAuditoria, ex.Message);

            }
        }

        /// <summary>
        /// Genera la auditoria a partir de los cambios que se hayan
        /// realizado en el contexto
        /// </summary>
        /// <typeparam name="TContexto">Ojecto de tipo OjectContext</typeparam>
        /// <param name="contextoActual">Cotexto Actual de modelo </param>
        public static void MetodoAuditoria<TContexto>(TContexto contextoActual)
            where TContexto : ObjectContext, new()
        {
            try
            {
                var almacenarDetalle = Convert.ToBoolean(ConfigurationManager.AppSettings["AuditoriaDetalle"]);
                var cambios = contextoActual.ObjectStateManager
                    .GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);

                var contextoAnterior = new TContexto();
                contextoAnterior.MetadataWorkspace.LoadFromAssembly(Assembly.GetAssembly(contextoAnterior.GetType()));

                var lista = new List<object>();
                // ReSharper disable LoopCanBeConvertedToQuery
                foreach (var entrada in cambios)
                // ReSharper restore LoopCanBeConvertedToQuery
                {
                    if (!entrada.IsRelationship && entrada.Entity != null && !(entrada.Entity is Auditoria))
                    {
                        if (entrada.State != EntityState.Added)
                        {
                            var key = entrada.EntityKey;
                            var objeto = contextoAnterior.GetObjectByKey(key);
                            lista.Add(objeto);
                        }
                    }
                }
                var datosAuditoria = ObetenerDatosAuditoria();
                try
                {
                    Procesar(contextoActual, lista, almacenarDetalle, datosAuditoria.Usuario, datosAuditoria.Ip, string.Empty);
                }
                catch (Exception ex)
                {
                    Procesar(contextoActual, lista, almacenarDetalle, datosAuditoria.Usuario, datosAuditoria.Ip, ex.Message);
                }
            }
            catch (Exception excepcion)
            {
                ControladorExcepciones.Procesar(excepcion);
            }
        }

        /// <summary>
        /// Genera la auditoria a partir de los cambios que se hayan realizado en el contexto
        /// </summary>
        /// <param name="error">Error producido</param>
        public static void MetodoAuditoria(string error)
        {
            try
            {
                var datosAuditoria = ObetenerDatosAuditoria();
                ProcesarAuditoriaError(datosAuditoria.Usuario, datosAuditoria.Ip, error);
            }
            catch
            {
                return;
            }
        }

        #endregion
    }
}